package com.company.domain.repository.factory;

import java.io.Serializable;

import javax.persistence.EntityManager;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.QueryExtractor;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.PersistenceProvider;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.QueryDslUtils;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.util.Assert;

import com.company.domain.repository.BaseRepositoryImpl;

public class BaseRepositoryFactory<R extends JpaRepository<T, I>, T, I extends Serializable>
		extends JpaRepositoryFactoryBean<R, T, I> {

	@Override
	protected RepositoryFactorySupport createRepositoryFactory(
			EntityManager entityManager) {
		return new MyRepositoryFactory(entityManager);
	}

	private static class MyRepositoryFactory extends JpaRepositoryFactory {

		@SuppressWarnings("unused")
		private EntityManager entityManager;

		@SuppressWarnings("unused")
		private final QueryExtractor extractor;

		public MyRepositoryFactory(EntityManager entityManager) {
			super(entityManager);
			Assert.notNull(entityManager);
			this.entityManager = entityManager;
			this.extractor = PersistenceProvider
					.fromEntityManager(entityManager);
		}

		@SuppressWarnings({ "unchecked", "rawtypes" })
		protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(
				RepositoryMetadata metadata, EntityManager entityManager) {
			Class<?> repositoryInterface = metadata.getRepositoryInterface();
			JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata
					.getDomainType());
			if (isQueryDslExecutor(repositoryInterface)) {
				return new QueryDslJpaRepository(entityInformation,
						entityManager);
			} else {
				return new BaseRepositoryImpl(entityInformation, entityManager,
						repositoryInterface);
			}
		}

		@Override
		protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
			if (isQueryDslExecutor(metadata.getRepositoryInterface())) {
				return QueryDslJpaRepository.class;
			} else {
				return BaseRepositoryImpl.class;
			}
		}

		private boolean isQueryDslExecutor(Class<?> repositoryInterface) {
			return QueryDslUtils.QUERY_DSL_PRESENT
					&& QueryDslPredicateExecutor.class
							.isAssignableFrom(repositoryInterface);
		}
	}
}